import GameApplication from '../../system/GameApplication';
import Mesh from '../Mesh/Mesh';
import GameObject from './GameObject';


interface RegProgramData {
    program: any,
    attribs: string[],
    uniforms: string[],
}

interface ProgramData {
    program: any,
    attribs: Map<string, any>,
    uniforms: Map<string, any>,
}


//世界对象
export default class Scene extends GameObject {

    public gl: any;
    public programs: Map<string, ProgramData>;
    public drawObjs: Map<string, GameObject[]>;

    public view: Mesh | null = null;

    public constructor(name: string, gl: any) {
        super(name);
        this.gl = gl;
        this.programs = new Map<string, ProgramData>;
        this.drawObjs = new Map<string, GameObject[]>;
    }

    public init() {
        const { gl, children } = this;
        children.forEach(element => {
            element.init(gl);
        });
    }


    public update(time: number) {
        super.update(time);

        // const pvM4 = GameApplication.getCamera()!.getPvM4();
        // this.setUniform('u_PvM4', { value: pvM4.elements });
    }

    public regProgram(programName: string, refProgramData: RegProgramData) {
        const { programs, gl } = this;
        const { program, attribs, uniforms } = refProgramData;
        if (typeof programs.get(programName) != 'undefined') {
            console.log(`[${programName}]已注册`)
            return;
        }

        let attribNames = new Map();
        let uniformNames = new Map();


        gl.useProgram(program);

        for (let att of attribs) {
            attribNames.set(att, gl.getAttribLocation(program, att));
        }
        for (let uni of uniforms) {
            uniformNames.set(uni, gl.getUniformLocation(program, uni));
        }
        programs.set(programName, {
            program: program,
            attribs: attribNames,
            uniforms: uniformNames
        });
    }

    protected _getChildren(obj3D: GameObject) {
        let arr: GameObject[] = [];
        const child = obj3D.children;
        if (child.length > 0) {
            for (let obj of child) {
                for (let i of this._getChildren(obj)) {
                    arr.push(i);
                }
            }
        }
        for (let i of child) {
            arr.unshift(i);//头插
        }
        return arr;
    }

    public add(...child: GameObject[]) {
        const { gl, children, drawObjs } = this;

        super.add(...child);
        child.forEach(u => {

            //递归出所有子类
            let child = this._getChildren(u);
            child.push(u);
            for (let obj of child) {
                u.init(gl);
                const mesh = obj as unknown as Mesh;
                if (mesh instanceof Mesh) {
                    this.addDrawObjs(mesh, drawObjs);
                }
            }
        });
    }

    public remove(element: GameObject) {
        const { children, drawObjs } = this;
        if (super.remove(element)) {

            let child = this._getChildren(element);
            child.push(element);
            for (let obj of child) {
                const mesh = obj as unknown as Mesh;
                if (mesh instanceof Mesh) {
                    this.removeDrawObjs(mesh, drawObjs);
                }
            }
            return true;
        }
        return false;
    }

    //如果父类对象已经加入scene中后再向父类对象添加元素, 则需要手动调用一次
    public addDrawObjs(mesh: Mesh, drawObjsMap: Map<string, GameObject[]>) {
        if (mesh.mat == null)
            return;

        const key = (mesh.mat as any).program;
        //查询key是否存在
        if (typeof drawObjsMap.get(key) == 'undefined') {
            //插入新的
            drawObjsMap.set(key, [mesh]);
        } else {
            //添加上
            drawObjsMap.get(key)!.push(mesh);
        }
    }

    //如果父类对象已经加入scene中后再向父类对象添加元素, 则需要手动调用一次
    public removeDrawObjs(mesh: Mesh, drawObjsMap: Map<string, GameObject[]>) {
        if (mesh.mat == null)
            return;
        const key = (mesh.mat as any).program;
        const arr = drawObjsMap.get(key)!;
        const index = arr.indexOf(mesh);
        arr.splice(index, 1);
    }

    public setUniform(name: string, uniform: any) {
        const { children } = this;
        children.forEach(u => {
            const mesh = u as unknown as Mesh;
            if (mesh instanceof Mesh) {
                mesh.setUniform(name, uniform);
            }
        })
    }
    public setAttrib(name: string, attrib: any) {
        const { children } = this;
        children.forEach(u => {
            const mesh = u as unknown as Mesh;
            if (mesh instanceof Mesh) {
                mesh.setAttrib(name, attrib);
            }
        })
    }
    public setMap(name: string, map: any) {
        const { children } = this;
        children.forEach(u => {
            const mesh = u as unknown as Mesh;
            if (mesh instanceof Mesh) {
                mesh.setMap(name, map);
            }
        })
    }

    public draw([r, g, b, a] = [0, 0, 0, 1]) {
        const { gl, programs, drawObjs } = this;

        gl.clearColor(r, g, b, a);
        gl.clear(gl.COLOR_BUFFER_BIT);

        for (let [key, value] of drawObjs) {
            const { program, attribs, uniforms } = programs.get(key)!;
            gl.useProgram(program);

            value.forEach(element => {
                const mesh = element as unknown as Mesh;
                if (mesh instanceof Mesh) {
                    mesh.updateData(gl, attribs, uniforms);
                    mesh.draw(gl);
                }
            });
        }
    }

    public updateView(texture1: any) {
        const { view, children, gl } = this;
        if (view == null) {
            this.view = this.getChildByName('view')! as Mesh;
        }
        this.view!.addTexureNew(gl, { 'u_Sampler': { texture: texture1 } });
    }

}